#include "SPCollision.h"
#include "SPD3D11RenderEnvironment.h"
#include "SPCamera.h"

//--------------------------------------------------------
// SPCollisionComponentRenderer
//--------------------------------------------------------
namespace SP
{
	class SPCollisionComponentRenderer : public SPD3DObject
	{
		friend void SPDrawCollisionComponent( SPCollisionComponent* pCC );
	protected:
		SPCollisionComponentRenderer();
		~SPCollisionComponentRenderer();
		HRESULT Create();

		void Draw( SPCollisionComponent* pCC );

		struct CB
		{
			XMFLOAT4 vColour;
			XMMATRIX mWorldViewProj;
		};

		ID3D11InputLayout* m_pVertexLayout;
		ID3D11VertexShader* m_pVertexShader;
		ID3D11PixelShader* m_pPixelShader;
		ID3D11Buffer* m_pCB;

		ID3D11Buffer* m_pCubVB;
		ID3D11Buffer* m_pCubIB;

		ID3D11Buffer* m_pSphereVB;
		ID3D11Buffer* m_pSphereIB;

		void DrawCollisionObj( SPCollisionObject* p );
		void DrawBox( OrientedBox* );
		void DrawSphere( Sphere* );
	};

	void SPDrawCollisionComponent( SPCollisionComponent* pCC )
	{
		static SPCollisionComponentRenderer staticSPCCR;
		staticSPCCR.Draw( pCC );
	}

	SPCollisionComponentRenderer::SPCollisionComponentRenderer()
	{
		Create();
	}

	SPCollisionComponentRenderer::~SPCollisionComponentRenderer()
	{
		SAFE_RELEASE( m_pVertexLayout );
		SAFE_RELEASE( m_pVertexShader );
		SAFE_RELEASE( m_pPixelShader );
		SAFE_RELEASE( m_pCB );
	}

	HRESULT SPCollisionComponentRenderer::Create()
	{
		HRESULT hr = S_OK;

		ID3DBlob* pVSBlob = NULL;
		ID3DBlob* pPSBlob = NULL;

		V_RETURN( SPGetRenderEnvironment()->CompileShaderFromFile( L"hlsl\\CollisionObj.hlsl", NULL, "VSMain", "vs_5_0", &pVSBlob ) );
		V_RETURN( SPGetRenderEnvironment()->CompileShaderFromFile( L"hlsl\\CollisionObj.hlsl", NULL, "PSMain", "ps_5_0", &pPSBlob ) );

		V_RETURN( m_pDevice->CreateVertexShader( pVSBlob->GetBufferPointer(  ), pVSBlob->GetBufferSize(  ), NULL, &m_pVertexShader ) );
		V_RETURN( m_pDevice->CreatePixelShader( pPSBlob->GetBufferPointer(  ), pPSBlob->GetBufferSize(  ), NULL, &m_pPixelShader ) );

		// Define the input layout
		D3D11_INPUT_ELEMENT_DESC layout[] =
		{
			{"POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0},
		};

		UINT numElements = ARRAYSIZE( layout );
		// Create the input layout
		V_RETURN( m_pDevice->CreateInputLayout( layout, numElements, pVSBlob->GetBufferPointer(  ),
			pVSBlob->GetBufferSize(  ), &m_pVertexLayout ) );

		SAFE_RELEASE( pVSBlob );
		SAFE_RELEASE( pPSBlob );

		// Create the constant buffers
		D3D11_BUFFER_DESC Desc;
		Desc.Usage = D3D11_USAGE_DYNAMIC;
		Desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		Desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		Desc.MiscFlags = 0;

		Desc.ByteWidth = sizeof( CB );
		V_RETURN( m_pDevice->CreateBuffer( &Desc, NULL, &m_pCB ) );

		// Create Cub Buffer
		DWORD indices[] =
		{
			0, 1,
			1, 2,
			2, 3,
			3, 0,
			4, 5,
			5, 6,
			6, 7,
			7, 4,
			0, 4,
			1, 5,
			2, 6,
			3, 7
		};
		V_RETURN( SPGetRenderEnvironment()->CreateBuffer( sizeof( DWORD ), 24, indices, &m_pCubIB ) );

		XMFLOAT4 vertices[8] =
		{
			XMFLOAT4( -1, -1, -1, 0 ),
			XMFLOAT4( 1, -1, -1, 0 ),
			XMFLOAT4( 1, -1, 1, 0 ),
			XMFLOAT4( -1, -1, 1, 0 ),
			XMFLOAT4( -1, 1, -1, 0 ),
			XMFLOAT4( 1, 1, -1, 0 ),
			XMFLOAT4( 1, 1, 1, 0 ),
			XMFLOAT4( -1, 1, 1, 0 )
		};
		V_RETURN( SPGetRenderEnvironment()->CreateBuffer( sizeof( XMFLOAT4 ), 8, vertices, &m_pCubVB ) );


		// Create Sphere Buffer
		static const DWORD dwRingSegments = 32;

		XMFLOAT4 verts[ dwRingSegments * 3 ];
		ZeroMemory( verts, sizeof( verts ) );
		DWORD ids[ dwRingSegments * 6 ];

		float delta = 1.0f / dwRingSegments;	
		float t = 0.0f;
		delta *= 3.14159265358979324 * 2;
		for (DWORD i = 0 ; i < dwRingSegments; i++)
		{
			DWORD x = i;
			DWORD y = i+dwRingSegments;
			DWORD z = i+dwRingSegments*2;

			verts[x].y = cos(t);	
			verts[x].z = sin(t);	
			ids[x*2] = x;			
			ids[x*2+1] = x + 1;

			verts[y].x = sin(t);		
			verts[y].z = cos(t);		
			ids[y*2] = y;
			ids[y*2+1] = y + 1;

			verts[z].x = sin(t);
			verts[z].y = cos(t);
			ids[z*2] = z;
			ids[z*2+1] = z+1;

			t += delta;
		}

		ids[dwRingSegments*2-1] = 0;
		ids[dwRingSegments*4-1] = dwRingSegments;
		ids[dwRingSegments*6-1] = dwRingSegments * 2;
		//ids[dwRingSegments*6-2] = dwRingSegments * 3 - 1;
		//ids[dwRingSegments*6-3] = dwRingSegments * 2;

		V_RETURN( SPGetRenderEnvironment()->CreateBuffer( sizeof( DWORD ), dwRingSegments * 6, ids, &m_pSphereIB ) );
		V_RETURN( SPGetRenderEnvironment()->CreateBuffer( sizeof( XMFLOAT4 ), dwRingSegments * 3, verts, &m_pSphereVB ) );

		return hr;
	}

	void SPCollisionComponentRenderer::Draw( SPCollisionComponent* pCC )
	{
		m_pImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_LINELIST );
		m_pImmediateContext->IASetInputLayout( m_pVertexLayout );
		m_pImmediateContext->PSSetShader( m_pPixelShader, NULL, 0 );
		m_pImmediateContext->VSSetShader(  m_pVertexShader, NULL, 0 );

		m_pImmediateContext->OMSetDepthStencilState( NULL, 0 );
		float vBlendFactor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
		m_pImmediateContext->OMSetBlendState( NULL, vBlendFactor, 0xFFFFFFFF );
		m_pImmediateContext->RSSetState( NULL );

		DrawCollisionObj( pCC->m_pOutline );
		
		for ( int i = 0; i < pCC->m_CollisionObjVector.size(); i++ )
		{
			DrawCollisionObj( pCC->m_CollisionObjVector[i] );
		}
		/*
		Sphere s;

		s.Center = XMFLOAT3( 1,0,0);
		s.Radius = 2;

		OrientedBox ob;

		ob.Extents = XMFLOAT3( 2,1,1);
		ob.Center = XMFLOAT3( -1,0,1);
		XMStoreFloat4( &ob.Orientation, XMQuaternionIdentity() );

		DrawBox( &ob );
		DrawSphere( &s );*/
	}

	void SPCollisionComponentRenderer::DrawCollisionObj( SPCollisionObject* p )
	{
		if ( p->m_nCollisionoObjectType == SPCollisionObject::eCollisionObj_Box )
		{
			OrientedBox* pBox = ( OrientedBox* )p->m_pCollisionObj;

			DrawBox( pBox );
		}

		if ( p->m_nCollisionoObjectType == SPCollisionObject::eCollisionObj_Sphere )
		{
			Sphere* pSphere = ( Sphere* )p->m_pCollisionObj;

			DrawSphere( pSphere );
		}
	}

	void SPCollisionComponentRenderer::DrawBox( OrientedBox* obb )
	{
		HRESULT hr;

		XMMATRIX mTransform;
		mTransform = XMMatrixScaling( obb->Extents.x, obb->Extents.y, obb->Extents.z );
		mTransform *= XMMatrixRotationQuaternion( XMLoadFloat4( &obb->Orientation ) );
		mTransform *= XMMatrixTranslationFromVector( XMLoadFloat3( &obb->Center ) );
		mTransform *= SPGetCamera()->GetViewProjMtx();

		D3D11_MAPPED_SUBRESOURCE MappedResource;
		V( m_pImmediateContext->Map( m_pCB, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ) );
		CB* pPerFrame = ( CB* )MappedResource.pData;
		pPerFrame->vColour = XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f );
		pPerFrame->mWorldViewProj = XMMatrixTranspose( mTransform );
		m_pImmediateContext->Unmap( m_pCB, 0 );
		m_pImmediateContext->VSSetConstantBuffers( 0, 1, &m_pCB );
		m_pImmediateContext->PSSetConstantBuffers( 0, 1, &m_pCB );

		UINT Strides[1];
		UINT Offsets[1];
		ID3D11Buffer* pVB[1];
		pVB[0] = m_pCubVB;
		Strides[0] = sizeof( XMFLOAT4 );
		Offsets[0] = 0;
		m_pImmediateContext->IASetVertexBuffers( 0, 1, pVB, Strides, Offsets );
		m_pImmediateContext->IASetIndexBuffer( m_pCubIB, DXGI_FORMAT_R32_UINT, 0 );

		m_pImmediateContext->DrawIndexed( 24, 0, 0 );
	}

	void SPCollisionComponentRenderer::DrawSphere( Sphere* sphere )
	{
		HRESULT hr;

		XMMATRIX mTransform;
		mTransform = XMMatrixScaling( sphere->Radius, sphere->Radius, sphere->Radius );
		mTransform *= XMMatrixTranslationFromVector( XMLoadFloat3( &sphere->Center ) );
		mTransform *= SPGetCamera()->GetViewProjMtx();

		D3D11_MAPPED_SUBRESOURCE MappedResource;
		V( m_pImmediateContext->Map( m_pCB, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ) );
		CB* pPerFrame = ( CB* )MappedResource.pData;
		pPerFrame->vColour = XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f );
		pPerFrame->mWorldViewProj = XMMatrixTranspose( mTransform );
		m_pImmediateContext->Unmap( m_pCB, 0 );
		m_pImmediateContext->VSSetConstantBuffers( 0, 1, &m_pCB );
		m_pImmediateContext->PSSetConstantBuffers( 0, 1, &m_pCB );

		UINT Strides[1];
		UINT Offsets[1];
		ID3D11Buffer* pVB[1];
		pVB[0] = m_pSphereVB;
		Strides[0] = sizeof( XMFLOAT4 );
		Offsets[0] = 0;
		m_pImmediateContext->IASetVertexBuffers( 0, 1, pVB, Strides, Offsets );
		m_pImmediateContext->IASetIndexBuffer( m_pSphereIB, DXGI_FORMAT_R32_UINT, 0 );

		m_pImmediateContext->DrawIndexed( 32 * 6, 0, 0 );		
	}
}


//---------------------------------------------------------
// SPCollisionObject
//---------------------------------------------------------
namespace SP
{
	SPCollisionObject::SPCollisionObject()
	{
		m_nCollisionoObjectType = eCollisionObj_None;
		m_pCollisionObj = NULL;
	}

	SPCollisionObject::SPCollisionObject( const Sphere* pSphere )
	{
		m_nCollisionoObjectType = eCollisionObj_Sphere;
		m_pCollisionObj = new Sphere();
		CopyMemory( m_pCollisionObj, pSphere, sizeof( Sphere ) );		
	}

	SPCollisionObject::SPCollisionObject( const OrientedBox* pBox )
	{
		m_nCollisionoObjectType = eCollisionObj_Box;
		m_pCollisionObj = new OrientedBox();
		CopyMemory( m_pCollisionObj, pBox, sizeof( OrientedBox ) );
	}

	void SPCollisionObject::SetObj( const OrientedBox* pBox )
	{
		if ( m_nCollisionoObjectType != eCollisionObj_Box )
		{
			m_nCollisionoObjectType = eCollisionObj_Box;
			SAFE_DELETE( m_pCollisionObj );
			m_pCollisionObj = new OrientedBox();	
		}
		CopyMemory( m_pCollisionObj, pBox, sizeof( OrientedBox ) );
	}

	void SPCollisionObject::SetObj( const Sphere* pSphere )
	{
		if ( m_nCollisionoObjectType != eCollisionObj_Sphere )
		{
			m_nCollisionoObjectType = eCollisionObj_Sphere;
			SAFE_DELETE( m_pCollisionObj );
			m_pCollisionObj = new Sphere();
		}	
		CopyMemory( m_pCollisionObj, pSphere, sizeof( Sphere ) );		
	}

	SPCollisionObject::~SPCollisionObject()
	{
		SAFE_DELETE( m_pCollisionObj );
	}

	bool SPCollisionObject::Intersect( SPCollisionObject* pOther )
	{
		switch ( m_nCollisionoObjectType )
		{
		case eCollisionObj_Box:
			switch( pOther->m_nCollisionoObjectType )
			{
			case eCollisionObj_Box:
				return IntersectOrientedBoxOrientedBox( (OrientedBox*)pOther->m_pCollisionObj, (OrientedBox*)m_pCollisionObj );
			case eCollisionObj_Sphere:
				return IntersectSphereOrientedBox( (Sphere*)pOther->m_pCollisionObj, (OrientedBox*)m_pCollisionObj );
			}
		case eCollisionObj_Sphere:
			switch( pOther->m_nCollisionoObjectType )
			{
			case eCollisionObj_Box:
				return IntersectSphereOrientedBox( (Sphere*)pOther->m_pCollisionObj, (OrientedBox*)m_pCollisionObj );
			case eCollisionObj_Sphere:
				return IntersectSphereSphere( (Sphere*)pOther->m_pCollisionObj, (Sphere*)m_pCollisionObj );
			}
		}
		return false;
	}

	bool SPCollisionObject::IntersectRay( FXMVECTOR Origin, FXMVECTOR Direction, FLOAT* pDist )
	{
		switch( m_nCollisionoObjectType )
		{
		case eCollisionObj_Box:
			return IntersectRayOrientedBox( Origin, Direction, (OrientedBox*)m_pCollisionObj, pDist );
		case eCollisionObj_Sphere:
			return IntersectRaySphere( Origin, Direction, (Sphere*)m_pCollisionObj, pDist );
		}
		return false;
	}

}

//-----------------------------------------------------------
// SPCollisionComponent
//-----------------------------------------------------------
namespace SP
{
	SPCollisionComponent::SPCollisionComponent()
	{

	}

	SPCollisionComponent::SPCollisionComponent( const WCHAR* path )
	{
		HANDLE hFile = CreateFile( path, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN,
			NULL );
		if( INVALID_HANDLE_VALUE == hFile )
			return;

		LARGE_INTEGER FileSize;
		GetFileSizeEx( hFile, &FileSize );
		UINT cBytes = FileSize.LowPart;

		BYTE* pBufPtr = new BYTE[cBytes];

		DWORD dwBytesRead;
		if( !ReadFile( hFile, pBufPtr, cBytes, &dwBytesRead, NULL ) )
			return;

		// close file
		CloseHandle( hFile );

		CreateFromFileBuffer( pBufPtr );
	
		SAFE_DELETE( pBufPtr );
	}

	void SPCollisionComponent::SaveCollisionComponentToFile( const WCHAR* path )
	{
		SPFileBuffer* pFB = new SPFileBuffer();
		CreateFileBuffer( pFB );

		HANDLE hFile = CreateFile( path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
		if( hFile == INVALID_HANDLE_VALUE )
		{
			return ;
		}
		DWORD dwBytesWritten;
		//WriteFile( hFile, &pFB->nNumByte, sizeof( int ), &dwBytesWritten, NULL );
		WriteFile( hFile, pFB->pFileBuffer, pFB->nNumByte, &dwBytesWritten, NULL );
		CloseHandle( hFile );

		SAFE_DELETE( pFB );
	}

	SPCollisionComponent::~SPCollisionComponent()
	{

	}

	bool SPCollisionComponent::Intersect( SPCollisionComponent* pOther )
	{
		if ( !m_pOutline->Intersect( pOther->m_pOutline ) )
			return false;

		SPCollisionObjectVector others = pOther->m_CollisionObjVector;

		SPCollisionObjectVector::iterator it1 = m_CollisionObjVector.begin();
		for( ; it1 != m_CollisionObjVector.end(); it1++ )
		{
			SPCollisionObjectVector::iterator it2 = others.begin();
			for ( ; it2 != others.end(); it2++ )
			{
				if ( ( *it1 )->Intersect( *it2 ) )
				{
					return true;
				}
			}
		}
		return false;
	}

	void SPCollisionComponent::Draw()
	{
		SPDrawCollisionComponent( this );
	}

	void SPCollisionComponent::CreateFileBuffer( SPFileBuffer* pFB )
	{	
		UINT bufferSize = sizeof( UINT ) + sizeof( OrientedBox );
		bufferSize *= 1 + m_CollisionObjVector.size();
		pFB->CreateBuffer( bufferSize + sizeof( UINT ) );

		BYTE* pBuf = pFB->pFileBuffer;
		UINT numObjs = m_CollisionObjVector.size();
		CopyMemory( pBuf, &numObjs, sizeof( UINT ) );
		pBuf += sizeof( UINT );

		// outline
		SPCollisionObject* pCO = m_pOutline;
		CopyMemory( pBuf, &pCO->m_nCollisionoObjectType, sizeof( UINT ) );
		pBuf += sizeof( UINT );
		UINT sizeofObj = ( pCO->m_nCollisionoObjectType == 1 ) ? sizeof( OrientedBox ) : sizeof( Sphere );
		CopyMemory( pBuf, pCO->m_pCollisionObj, sizeofObj );
		pBuf += sizeofObj;

		// sub
		for ( int i = 0; i < m_CollisionObjVector.size(); i++ )
		{
			pCO = m_CollisionObjVector[i];
			CopyMemory( pBuf, &pCO->m_nCollisionoObjectType, sizeof( UINT ) );
			pBuf += sizeof( UINT );
			sizeofObj = ( pCO->m_nCollisionoObjectType == 1 ) ? sizeof( OrientedBox ) : sizeof( Sphere );
			CopyMemory( pBuf, pCO->m_pCollisionObj, sizeofObj );
			pBuf += sizeofObj;
		}
	}

	void SPCollisionComponent::CreateFromFileBuffer( BYTE* pBufPtr )
	{
		UINT* numObjs = ( UINT* )pBufPtr;
		pBufPtr += sizeof( UINT );

		// outline
		UINT* nType = ( UINT* )pBufPtr;
		pBufPtr += sizeof( UINT );

		if ( *nType == 1 )
		{
			OrientedBox* pBox = ( OrientedBox* )pBufPtr;
			pBufPtr += sizeof( OrientedBox );
			
			m_pOutline = new SPCollisionObject( pBox );
		}
		else
		{
			Sphere* pSphere = ( Sphere* )pBufPtr;
			pBufPtr += sizeof( Sphere );

			m_pOutline = new SPCollisionObject( pSphere );
		}

		// sub
		for ( int i = 0; i < *numObjs; i++ )
		{
			nType = ( UINT* )pBufPtr;
			pBufPtr += sizeof( UINT );

			if ( *nType == 1 )
			{
				OrientedBox* pBox = ( OrientedBox* )pBufPtr;
				pBufPtr += sizeof( OrientedBox );

				m_CollisionObjVector.push_back( new SPCollisionObject( pBox ) );
			}
			else
			{
				Sphere* pSphere = ( Sphere * )pBufPtr;
				pBufPtr += sizeof( Sphere );

				m_CollisionObjVector.push_back( new SPCollisionObject( pSphere ) );
			}
		}

	}


}